#include <QOpenGLTexture>
#include <QOpenGLBuffer>
#include <QMouseEvent>
#include "CPlayWidget.h"


#define UNIFORM_2D "uniform sampler2D"
#define VERTEX_SHADER_IN "attribute"
#define VERTEX_SHADER_OUT "varying"
#define FRAGMENT_SHADER_IN "varying"
#define FRAGMENT_SHADER_OUT
#define FRAGMENT_SHADER_COLOR "gl_FragColor"
#define FRAGMENT_SHADER_TEXTURE "texture2D"

static const char kI420FragmentShaderSource[] =
        FRAGMENT_SHADER_IN " vec2 textureOut;\n"
        UNIFORM_2D " tex_y;\n"
        UNIFORM_2D " tex_u;\n"
        UNIFORM_2D " tex_v;\n"
        FRAGMENT_SHADER_OUT
        "void main() {\n"
        "    float y, u, v, r, g, b;\n"
        "    y = " FRAGMENT_SHADER_TEXTURE "(tex_y, textureOut).r;\n"
        "    u = " FRAGMENT_SHADER_TEXTURE "(tex_u, textureOut).r;\n"
        "    v = " FRAGMENT_SHADER_TEXTURE "(tex_v, textureOut).r;\n"
        "    y = y * 1.1643828125;\n"
        "    r = y + 1.59602734375 * v - 0.87078515625;\n"
        "    g = y - 0.39176171875 * u - 0.81296875 * v + 0.52959375;\n"
        "    b = y + 2.017234375   * u - 1.081390625;\n"

"    " FRAGMENT_SHADER_COLOR " = vec4(r, g, b, 1.0);\n"
        "  }\n";


const char *vsrcyuv = "attribute vec4 vertexIn; \
attribute vec2 textureIn; \
varying vec2 textureOut;  \
void main(void)           \
{                         \
    gl_Position = vertexIn; \
    textureOut = textureIn; \
}";


const char *fsrcyuv = "varying vec2 textureOut; \
uniform sampler2D tex_y; \
uniform sampler2D tex_u; \
uniform sampler2D tex_v; \
void main(void) \
{ \
    vec3 yuv; \
    vec3 rgb; \
    yuv.x = texture2D(tex_y, textureOut).r; \
    yuv.y = texture2D(tex_u, textureOut).r - 0.5; \
    yuv.z = texture2D(tex_v, textureOut).r - 0.5; \
    rgb = mat3( 1,       1,         1, \
                0,       -0.39465,  2.03211, \
                1.13983, -0.58060,  0) * yuv; \
    gl_FragColor = vec4(rgb, 1); \
}";

const char *fsrcrgb = "varying vec2 textureOut;  \
        uniform sampler2D rgbdata; \
        void main() \
        { \
            gl_FragColor = texture(rgbdata, textureOut);  \
        }";


void CPlayWidget::OnPaintData(const rtc::scoped_refptr<webrtc::I420BufferInterface>& buffer)
{
    /*
    if(nullptr == m_pBufYuv420p)
    {
        m_pBufYuv420p = new unsigned char[len];
        qDebug("CPlayWidget::PlayOneFrame new data memory. Len=%d width=%d height=%d\n",
               len, m_nVideoW, m_nVideoW);
        memcpy(m_pBufYuv420p, data,len);
        update();
    }
    */
    m_buffer = buffer;
    update();
}

CPlayWidget::CPlayWidget(QWidget *parent, IMG_TYPE type)
{
    textureUniformY = 0;
    textureUniformU = 0;
    textureUniformV = 0;
    id_y = 0;
    id_u = 0;
    id_v = 0;
    m_pTextureRGB = nullptr;
    m_pBufYuv420p = nullptr;
    m_pVSHader = NULL;
    m_pFSHader = NULL;
    m_pShaderProgram = NULL;
    m_pTextureY = NULL;
    m_pTextureU = NULL;
    m_pTextureV = NULL;
    m_nVideoH = 0;
    m_nVideoW = 0;
    mType = type;
    m_start_render = false;
}

CPlayWidget::CPlayWidget(QWidget *parent):QOpenGLWidget(parent) {
    textureUniformY = 0;
    textureUniformU = 0;
    textureUniformV = 0;
    id_y = 0;
    id_u = 0;
    id_v = 0;
    m_pTextureRGB = nullptr;
    m_pBufYuv420p = nullptr;
    m_pVSHader = NULL;
    m_pFSHader = NULL;
    m_pShaderProgram = NULL;
    m_pTextureY = NULL;
    m_pTextureU = NULL;
    m_pTextureV = NULL;
    m_nVideoH = 0;
    m_nVideoW = 0;
    mType = TYPE_YUV420P;
    m_start_render = false;
}

CPlayWidget::~CPlayWidget() {

}


int CPlayWidget::SetDataType(CPlayWidget::IMG_TYPE type){
    this->mType = type;
    initializeGL();
    return 0;
}

int CPlayWidget::StartRender()
{
    this->m_start_render = true;
    return 0;
}

int CPlayWidget::StopRender()
{
    this->m_start_render = false;
    return 0;
}

void CPlayWidget::OnCameraData( rtc::scoped_refptr<webrtc::I420BufferInterface> &buffer)
{
    m_buffer = buffer;
    memcpy(this->m_pBufYuv420p,buffer->GetI420()->DataY(),m_nVideoW*m_nVideoH);
    memcpy(this->m_pBufYuv420p + m_nVideoW*m_nVideoH ,buffer->GetI420()->DataU(),m_nVideoW*m_nVideoH/4);
    memcpy(this->m_pBufYuv420p+ m_nVideoW*m_nVideoH + m_nVideoW*m_nVideoH/4,
           buffer->GetI420()->DataV(),m_nVideoW*m_nVideoH/4);

    update();
}



int CPlayWidget::OnCameraData(uint8_t *p)
{
    memcpy(m_pBufYuv420p,p,m_nVideoH*m_nVideoW/2*3);
    update();
    return 0;
}

int CPlayWidget::SetImgSize(uint32_t width, uint32_t height)
{
    m_nVideoH = height;
    m_nVideoW = width;
    if(mType == TYPE_RGB32){
        m_pBufRgb32 = new uint8_t[width * height *4];
    }
    if(mType == TYPE_YUV420P){
        m_pBufYuv420p = new uint8_t[width * height *3/2];
    }
    if(mType == TYPE_I420){
        m_pBufYuv420p = new uint8_t[width * height *3/2];
    }
    return 0;
}

int CPlayWidget::RenderWidth()
{
    return this->m_nVideoW;
}

int CPlayWidget::RenderHeight()
{
    return this->m_nVideoH;
}


/*

 * Y = 0.299 R + 0.587 G + 0.114 B

U = - 0.1687 R - 0.3313 G + 0.5 B + 128

V = 0.5 R - 0.4187 G - 0.0813 B + 128

RGB YUV (256)

R = Y + 1.402 (Cr-128)

G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128)

B = Y + 1.772 (Cb-128)
*/
void CPlayWidget::initializeGL()
{
    initializeOpenGLFunctions();
    glEnable(GL_DEPTH_TEST);

    m_pVSHader = new QOpenGLShader(QOpenGLShader::Vertex, this);
    bool bCompile = m_pVSHader->compileSourceCode(vsrcyuv);
    if(!bCompile)
    {

    }
    m_pFSHader = new QOpenGLShader(QOpenGLShader::Fragment, this);
    if(mType == TYPE_RGB32){
        bCompile = m_pFSHader->compileSourceCode(fsrcrgb);
    }
    if(mType == TYPE_YUV420P){
        bCompile = m_pFSHader->compileSourceCode(fsrcyuv);
    }
    if(mType == TYPE_I420){
        bCompile = m_pFSHader->compileSourceCode(kI420FragmentShaderSource);
    }
    if(!bCompile)
    {

    }
#define PROGRAM_VERTEX_ATTRIBUTE 0
#define PROGRAM_TEXCOORD_ATTRIBUTE 1
    m_pShaderProgram = new QOpenGLShaderProgram;
    m_pShaderProgram->addShader(m_pFSHader);
    m_pShaderProgram->addShader(m_pVSHader);
    m_pShaderProgram->bindAttributeLocation("vertexIn", ATTRIB_VERTEX);
    m_pShaderProgram->bindAttributeLocation("textureIn", ATTRIB_TEXTURE);
    m_pShaderProgram->link();
    m_pShaderProgram->bind();
    if(this->mType == TYPE_YUV420P){
        initShaderYuv();
    }
    if(this->mType == TYPE_RGB32){
        initShaderRgb();
    }
    if(this->mType == TYPE_I420){
        initShaderI420();
    }
    glClearColor(0.0,0.0,0.0,0.0);
}


void CPlayWidget::resizeGL(int w, int h)
{
    if(h == 0)
    {
        h = 1;
    }
    glViewport(0,0, w,h);
}

void CPlayWidget::paintGL()
{
    if(m_start_render){
        if(mType == TYPE_YUV420P)
            loadYuvTexture();
        if(mType == TYPE_RGB32){
            loadRgbTexture();
        }
        if(mType == TYPE_I420){
            loadYuvTexture();
        }

        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    }
    return;
}

void CPlayWidget::initShaderYuv()
{
    textureUniformY = m_pShaderProgram->uniformLocation("tex_y");
    textureUniformU =  m_pShaderProgram->uniformLocation("tex_u");
    textureUniformV =  m_pShaderProgram->uniformLocation("tex_v");
    static const GLfloat vertexVertices[] = {
     -1.0f, -1.0f,
      1.0f, -1.0f,
      -1.0f, 1.0f,
      1.0f, 1.0f,
    };
    static const GLfloat textureVertices[] = {
     0.0f,  1.0f,
     1.0f,  1.0f,
     0.0f,  0.0f,
     1.0f,  0.0f,
    };
    glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices);
    glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);
    glEnableVertexAttribArray(ATTRIB_VERTEX);
    glEnableVertexAttribArray(ATTRIB_TEXTURE);

    m_pTextureY = new QOpenGLTexture(QOpenGLTexture::Target2D);
    m_pTextureU = new QOpenGLTexture(QOpenGLTexture::Target2D);
    m_pTextureV = new QOpenGLTexture(QOpenGLTexture::Target2D);
    m_pTextureY->create();
    m_pTextureU->create();
    m_pTextureV->create();
    id_y = m_pTextureY->textureId();
    id_u = m_pTextureU->textureId();
    id_v = m_pTextureV->textureId();
}

void CPlayWidget::initShaderRgb()
{
    textureUniformRGB = m_pShaderProgram->uniformLocation("rgbdata");
    static const GLfloat vertexVertices[] = {
    -1.0f, -1.0f,
    1.0f, -1.0f,
    -1.0f, 1.0f,
    1.0f, 1.0f,
    };

    static const GLfloat textureVertices[] = {
        0.0f,  0.0f,
        1.0f,  0.0f,
        0.0f,  1.0f,
        1.0f,  1.0f,
    };
    glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices);
    glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);
    glEnableVertexAttribArray(ATTRIB_VERTEX);
    glEnableVertexAttribArray(ATTRIB_TEXTURE);
    m_pTextureRGB = new QOpenGLTexture(QOpenGLTexture::Target2D);
    m_pTextureRGB->create();
    id_rgb = m_pTextureRGB->textureId();
}


void CPlayWidget::initShaderI420()
{
    textureUniformY = m_pShaderProgram->uniformLocation("tex_y");
    textureUniformU =  m_pShaderProgram->uniformLocation("tex_u");
    textureUniformV =  m_pShaderProgram->uniformLocation("tex_v");
    static const GLfloat vertexVertices[] = {
     -1.0f, -1.0f,
      1.0f, -1.0f,
      -1.0f, 1.0f,
      1.0f, 1.0f,
    };
    static const GLfloat textureVertices[] = {
     0.0f,  1.0f,
     1.0f,  1.0f,
     0.0f,  0.0f,
     1.0f,  0.0f,
    };
    glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices);
    glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);
    glEnableVertexAttribArray(ATTRIB_VERTEX);
    glEnableVertexAttribArray(ATTRIB_TEXTURE);
    m_pTextureY = new QOpenGLTexture(QOpenGLTexture::Target2D);
    m_pTextureU = new QOpenGLTexture(QOpenGLTexture::Target2D);
    m_pTextureV = new QOpenGLTexture(QOpenGLTexture::Target2D);
    m_pTextureY->create();
    m_pTextureU->create();
    m_pTextureV->create();
    id_y = m_pTextureY->textureId();
    id_u = m_pTextureU->textureId();
    id_v = m_pTextureV->textureId();
}


int CPlayWidget::loadYuvTexture()
{
    if(nullptr == m_pBufYuv420p)
        return 0;
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, id_y);
    glTexImage2D(GL_TEXTURE_2D,
          0,
          GL_RED,
          m_nVideoW,
          m_nVideoH,
          0,
          GL_RED,
          GL_UNSIGNED_BYTE,
          m_pBufYuv420p);

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, id_u);
    glTexImage2D(GL_TEXTURE_2D,
              0, GL_RED,
              m_nVideoW/2,
              m_nVideoH/2,
              0,
              GL_RED,
              GL_UNSIGNED_BYTE,
              m_pBufYuv420p + m_nVideoW*m_nVideoH);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glActiveTexture(GL_TEXTURE2);//  GL_TEXTURE2
    glBindTexture(GL_TEXTURE_2D, id_v);

    glTexImage2D(GL_TEXTURE_2D,
              0, GL_RED,
              m_nVideoW/2,
              m_nVideoH/2,
              0, GL_RED,
              GL_UNSIGNED_BYTE,
              m_pBufYuv420p + m_nVideoW*m_nVideoH +  m_nVideoW*m_nVideoH/4);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glUniform1i(textureUniformY, 0);
    glUniform1i(textureUniformU, 1);
    glUniform1i(textureUniformV, 2);
    return 0;
}

int CPlayWidget::loadRtcI420Texture()
{
    if(nullptr == m_buffer)
        return 0;
    glActiveTexture(GL_TEXTURE0);

    glBindTexture(GL_TEXTURE_2D, id_y);
    glPixelStorei(GL_UNPACK_ROW_LENGTH, m_buffer->StrideY());
    glTexImage2D(GL_TEXTURE_2D,

              0,
              GL_RED,
              m_nVideoW,
              m_nVideoH,
              0,
              GL_RED,
              GL_UNSIGNED_BYTE,
              m_buffer->DataY());

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, id_u);
    glPixelStorei(GL_UNPACK_ROW_LENGTH, m_buffer->StrideU());
    glTexImage2D(GL_TEXTURE_2D,
              0, GL_RED,
              m_nVideoW/2,
              m_nVideoH/2,
              0,
              GL_RED,
              GL_UNSIGNED_BYTE,
              m_buffer->DataU());
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glActiveTexture(GL_TEXTURE2);//GL_TEXTURE2
    glBindTexture(GL_TEXTURE_2D, id_v);
    glPixelStorei(GL_UNPACK_ROW_LENGTH, m_buffer->StrideV());
    glTexImage2D(GL_TEXTURE_2D,
              0, GL_RED,
              m_nVideoW/2,
              m_nVideoH/2,
              0, GL_RED,
              GL_UNSIGNED_BYTE,
              m_buffer->DataV());
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glUniform1i(textureUniformY, 0);
    glUniform1i(textureUniformU, 1);
    glUniform1i(textureUniformV, 2);
    return 0;
}

int CPlayWidget::loadRgbTexture()
{
     glActiveTexture(GL_TEXTURE0);
     glBindTexture(GL_TEXTURE_2D, id_rgb);
     glTexImage2D(GL_TEXTURE_2D,
                  0,
                  GL_RGBA,
                  m_nVideoW,
                  m_nVideoH,
                  0,
                  GL_BGRA,
                  GL_UNSIGNED_BYTE,
                  m_pBufRgb32);
     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
     glUniform1i(textureUniformRGB, 0);
     return 0;
}

